#include "../config.h"

#include <list>
#include <gtkmm.h>
#include <glibmm.h>
#include <glibmm/i18n.h>

#ifndef DISABLE_SS_XSS
# include <gdk/gdkx.h>
# include <X11/X.h>
# include <X11/Xlib.h>
# include <X11/Xatom.h>
#endif

#ifdef USE_DBUS_GLIB
# define DBUS_API_SUBJECT_TO_CHANGE
# include <dbus/dbus.h>
# include <dbus/dbus-glib.h>
# include <dbus/dbus-glib-lowlevel.h>
#endif

#include "log.h"
#include "misc.h"
#include "screensaver.h"

namespace gpyp {
namespace ss {

//========================
// Provider
const Glib::ustring &Provider::get_name() const {
  return name_;
}

Provider::Provider(const Glib::ustring &name) :
  name_(name)
{
}
Provider::~Provider() {
}

//========================
// XSSProvider
// xscreensaver-commandのコードをコピペして少しいじったもの
#ifndef DISABLE_SS_XSS
class XSSProvider;
typedef boost::shared_ptr<XSSProvider> RefXSSProvider;
class XSSProvider : public Provider {
private:
  static Atom XA_SCREENSAVER_VERSION;
  static Atom XA_SCREENSAVER_STATUS;
  static Atom XA_BLANK;
  static Atom XA_LOCK;

  static XErrorHandler old_handler;
  static bool got_badwindow;

  static int badwindow_error_handler(Display *display, XErrorEvent *error) {
    if (error->error_code == BadWindow) {
      got_badwindow = true;
      return 0;
    } else {
      return (*old_handler)(display, error);
    }
  }
  static Window find_window(std::string *version = NULL) {
    Display *display = gdk_x11_get_default_xdisplay();
    Window root = RootWindowOfScreen(DefaultScreenOfDisplay(display));
    Window root2, parent, *kids = NULL;
    unsigned int nkids;

    if (!XQueryTree(display, root, &root2, &parent, &kids, &nkids) ||
        root != root2 || parent || !kids || !nkids) {
      return 0;
    }

    Window res = 0;
    for(size_t i = 0; i < nkids; i++) {
      Atom type;
      int format;
      unsigned long nitems, bytesafter;
      unsigned char *v;
      int status;

      XSync(display, False);
      if (old_handler) break;
      got_badwindow = false;
      old_handler = XSetErrorHandler(badwindow_error_handler);
      status = XGetWindowProperty(display, kids[i],
                                  XA_SCREENSAVER_VERSION,
                                  0, 200, False, XA_STRING,
                                  &type, &format, &nitems, &bytesafter,
                                  &v);
      XSync(display, False);
      XSetErrorHandler(old_handler);
      old_handler = NULL;

      if (got_badwindow) {
        status = BadWindow;
        got_badwindow = false;
      }

      if (status == Success && type != None) {
        res = kids[i];
        if (v) {
          if (version) *version = (char *)v;
          free(v);
        }
        break;
      }
      if (v) free(v);
    }
    if (kids) XFree(kids);
    return res;
  }
  static bool is_active() {
    Display *display = gdk_x11_get_default_xdisplay();

    std::string version;
    Window window = find_window(&version);
    if (!window || version.empty()) {
      XSync(display, 0);
      throw Exception(_("failed to find XSS window"));
      return false;
    }

    /*
    XWindowAttributes xgwa;
    XGetWindowAttributes(display, window, &xgwa);
    XSelectInput(display, window, xgwa.your_event_mask | PropertyChangeMask);
    */

    /*
    XClassHint hint;
    memset(&hint, 0, sizeof(hint));
    XGetClassHint(display, window, &hint);
    if (!hint.res_class) {
      XSync(display, 0);
      return false;
    }
    */

    bool res = false;

    Atom type;
    int format;
    unsigned long nitems, bytesafter;
    unsigned char *data_p = NULL;
    if (XGetWindowProperty(display, RootWindow(display, 0),
                           XA_SCREENSAVER_STATUS,
                           0, 999, False, XA_INTEGER,
                           &type, &format, &nitems, &bytesafter,
                           &data_p) == Success &&
        type == XA_INTEGER && nitems >= 3 && data_p) {
      Atom *data = (Atom *)data_p;
      res = data[0] == XA_BLANK || data[0] == XA_LOCK;
    }
    if (data_p) XFree(data_p);
    XSync(display, 0);
    return res;
  }

public:
  static RefXSSProvider create() {
    Display *display = gdk_x11_get_default_xdisplay();
    XA_SCREENSAVER_VERSION =
      XInternAtom(display, "_SCREENSAVER_VERSION", False);
    XA_SCREENSAVER_STATUS =
      XInternAtom(display, "_SCREENSAVER_STATUS", False);
    XA_BLANK = XInternAtom(display, "BLANK", False);
    XA_LOCK = XInternAtom(display, "LOCK", False);
    try {
      bool active = is_active();
      log::log << "xss: ScreenSaver " << (active ? "On" : "Off") << log::endl;
      return RefXSSProvider(new XSSProvider());
    } catch(const Exception &ex) {
      log::err << "xss: " << ex.what() << log::endl;
      return RefXSSProvider();
    }
  }

  virtual bool get_active() const {
    try {
      return is_active();
    } catch(...) {
      return false;
    }
  }

protected:
  XSSProvider() :
    Provider("xss")
  {
  }
};

Atom XSSProvider::XA_SCREENSAVER_VERSION = 0;
Atom XSSProvider::XA_SCREENSAVER_STATUS = 0;
Atom XSSProvider::XA_BLANK = 0;
Atom XSSProvider::XA_LOCK = 0;

XErrorHandler XSSProvider::old_handler = NULL;
bool XSSProvider::got_badwindow = false;
#endif

#ifdef USE_DBUS_GLIB
//========================
// DBusProvider
/* dbus-send --session                                          \
             --dest=DBusProvider::service_                      \
             --type=method_call                                 \
             --print-reply                                      \
             --reply-timeout=20000                              \
             DBusProvider::path_                                \
             DBusProvider::interface_.DBusProvider::method_

   dbus-monitor --session                                               \
     "type='signal',interface='DBusProvider::interface_',member='DBusProvider::signal_'"
*/
class DBusProvider;
typedef boost::shared_ptr<DBusProvider> RefDBusProvider;
class DBusProvider : public Provider {
private:
  Glib::ustring service_;
  Glib::ustring path_;
  Glib::ustring interface_;
  Glib::ustring method_;

  static bool check_active(const Glib::ustring &name,
                           const Glib::ustring &service,
                           const Glib::ustring &path,
                           const Glib::ustring &interface,
                           const Glib::ustring &method,
                           bool                &active) {
    GError          *err   = NULL;
    DBusGConnection *conn  = NULL;
    DBusGProxy      *proxy = NULL;
    conn = dbus_g_bus_get(DBUS_BUS_SESSION, &err);
    if (!conn) log::err << name << ": " << err->message << log::endl;
    if (err) g_error_free(err);
    if (!conn) return false;
    proxy = dbus_g_proxy_new_for_name(conn,
                                      service.c_str(),
                                      path.c_str(),
                                      interface.c_str());
    if (!proxy) {
      log::err << name << ": " << err->message << log::endl;
      g_error_free(err);
      dbus_g_connection_unref(conn);
      return false;
    }
    if (err) g_error_free(err);

    gboolean ret = FALSE, res = FALSE;
    if ((res = dbus_g_proxy_call(proxy,
                                 method.c_str(),
                                 &err,
                                 G_TYPE_INVALID,
                                 G_TYPE_BOOLEAN, &ret,
                                 G_TYPE_INVALID))) {
      active = ret ? true : false;
    } else {
      log::err << name << ": " << err->message << log::endl;
    }
    if (err) g_error_free(err);
    g_object_unref(G_OBJECT(proxy));
    dbus_g_connection_unref(conn);
    return res ? true : false;
  }

public:
  static RefDBusProvider create(const Glib::ustring &name,
                                const Glib::ustring &service,
                                const Glib::ustring &path,
                                const Glib::ustring &interface,
                                const Glib::ustring &method) {
    Glib::ustring name_s = Glib::ustring("dbus-") + name;
    bool active = false;
    if (check_active(name_s, service, path, interface, method, active)) {
      log::log << name_s << ": ScreenSaver "
               << (active ? "On" : "Off") << log::endl;
      return RefDBusProvider(new DBusProvider(name_s,
                                              service,
                                              path,
                                              interface,
                                              method));
    } else {
      return RefDBusProvider();
    }
  }

  bool get_active() const {
    bool active = false;
    return check_active(get_name(),
                        service_,
                        path_,
                        interface_,
                        method_,
                        active) && active;
  }

protected:
  DBusProvider(const Glib::ustring &name,
               const Glib::ustring &service,
               const Glib::ustring &path,
               const Glib::ustring &interface,
               const Glib::ustring &method) :
    Provider   (name),
    service_   (service),
    path_      (path),
    interface_ (interface),
    method_    (method)
  {
  }
};
#endif

//========================
RefProvider _provider;

sigc::signal<void, RefProvider> signal_provider_changed_;
bool set_provider(RefProvider provider) {
  if (_provider || !provider) {
    return false;
  } else {
    _provider = provider;
    signal_provider_changed_(_provider);
    return true;
  }
}

void init() {
  RefProvider provider;
#ifdef USE_DBUS_GLIB
# ifndef DISABLE_SS_GNOME
  if (!provider) {
    provider = DBusProvider::create("gnome",
                                    "org.gnome.ScreenSaver",
                                    "/org/gnome/ScreenSaver",
                                    "org.gnome.ScreenSaver",
                                    "GetActive");
  }
# endif
# ifndef DISABLE_SS_KDE
  if (!provider) {
    provider = DBusProvider::create("kde4",
                                    "org.kde.screensaver",
                                    "/ScreenSaver",
                                    "org.freedesktop.ScreenSaver",
                                    "GetActive");
  }
# endif
#endif
#ifndef DISABLE_SS_XSS
  if (!provider) provider = XSSProvider::create();
#endif
  if (provider) set_provider(provider);
}
void uninit() {
  _provider.reset();
}

sigc::signal<void, RefProvider> signal_provider_changed() {
  return signal_provider_changed_;
}
RefProvider get_provider() {
  return _provider;
}
bool register_provider(RefProvider provider) {
  if (set_provider(provider)) {
    log::err << "SS: provider registered - "
             << provider->get_name() << log::endl;
    return true;
  } else {
    return false;
  }
}
bool get_active() {
  if (_provider) {
    return _provider->get_active();
  } else {
    return false;
  }
}

}/* namespace ss */
}/* namespace gpyp */
